eu.bittrade.libs.steemj.base.models.Transaction.java Source code

Java tutorial

Introduction

Here is the source code for eu.bittrade.libs.steemj.base.models.Transaction.java

Source

/*
 *     This file is part of SteemJ (formerly known as 'Steem-Java-Api-Wrapper')
 * 
 *     SteemJ is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     SteemJ is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.bittrade.libs.steemj.base.models;

import java.io.Serializable;
import java.security.InvalidParameterException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.joou.UInteger;
import org.joou.UShort;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;

import eu.bittrade.crypto.core.CryptoUtils;
import eu.bittrade.libs.steemj.configuration.SteemJConfig;
import eu.bittrade.libs.steemj.enums.PrivateKeyType;
import eu.bittrade.libs.steemj.exceptions.SteemInvalidTransactionException;
import eu.bittrade.libs.steemj.fc.TimePointSec;
import eu.bittrade.libs.steemj.interfaces.SignatureObject;
import eu.bittrade.libs.steemj.protocol.operations.Operation;

/**
 * This class represents a Steem "transaction" object.
 * 
 * @author <a href="http://Steemit.com/@dez1337">dez1337</a>
 */
public class Transaction implements Serializable {
    /** Generated serial version uid. */
    private static final long serialVersionUID = -3834759301983200246L;
    private static final Logger LOGGER = LoggerFactory.getLogger(Transaction.class);

    /**
     * The ref_block_num indicates a particular block in the past by referring
     * to the block number which has this number as the last two bytes.
     */
    @JsonProperty("ref_block_num")
    protected UShort refBlockNum;
    /**
     * The ref_block_prefix on the other hand is obtain from the block id of
     * that particular reference block.
     */
    @JsonProperty("ref_block_prefix")
    protected UInteger refBlockPrefix;
    @JsonProperty("expiration")
    protected transient TimePointSec expirationDate;
    @JsonProperty("operations")
    protected transient List<Operation> operations;
    // Original type is "extension_type" which is an array of "future_extions".
    @JsonProperty("extensions")
    protected transient List<FutureExtensions> extensions;

    /**
     * Create a new transaction object.
     * 
     * @param refBlockNum
     *            The reference block number (see
     *            {@link #setRefBlockNum(UShort)}).
     * @param refBlockPrefix
     *            The reference block index (see
     *            {@link #setRefBlockPrefix(UInteger)}).
     * @param expirationDate
     *            Define until when the transaction has to be processed (see
     *            {@link #setExpirationDate(TimePointSec)}).
     * @param operations
     *            A list of operations to process within this Transaction (see
     *            {@link #setOperations(List)}).
     * @param extensions
     *            Extensions are currently not supported and will be ignored
     *            (see {@link #setExtensions(List)}).
     */
    @JsonCreator
    public Transaction(@JsonProperty("ref_block_num") UShort refBlockNum,
            @JsonProperty("ref_block_prefix") UInteger refBlockPrefix,
            @JsonProperty("expiration") TimePointSec expirationDate,
            @JsonProperty("operations") List<Operation> operations,
            @JsonProperty("extensions") List<FutureExtensions> extensions) {
        this.setRefBlockNum(refBlockNum);
        this.setRefBlockPrefix(refBlockPrefix);
        this.setExpirationDate(expirationDate);
        this.setOperations(operations);
        this.setExtensions(extensions);
    }

    /**
     * Like {@link #Transaction(UShort, UInteger, TimePointSec, List, List)},
     * but allows you to provide a
     * {@link eu.bittrade.libs.steemj.base.models.BlockId} object as the
     * reference block and will also set the <code>expirationDate</code> to the
     * latest possible time.
     * 
     * @param blockId
     *            The block reference (see {@link #setRefBlockNum(UShort)} and
     *            {@link #setRefBlockPrefix(UInteger)}).
     * @param operations
     *            A list of operations to process within this Transaction (see
     *            {@link #setOperations(List)}).
     * @param extensions
     *            Extensions are currently not supported and will be ignored
     *            (see {@link #setExtensions(List)}).
     */
    public Transaction(BlockId blockId, List<Operation> operations, List<FutureExtensions> extensions) {
        this.setRefBlockNum(UShort.valueOf(blockId.getNumberFromHash() & 0xffff));
        this.setRefBlockPrefix(blockId.getHashValue());
        this.setExpirationDate(new TimePointSec(
                System.currentTimeMillis() + SteemJConfig.getInstance().getMaximumExpirationDateOffset() - 60000L));
        this.setOperations(operations);
        this.setExtensions(extensions);
    }

    /**
     * <b>This method is only used by JUnit-Tests</b>
     * 
     * Create a new signed transaction object.
     */
    @VisibleForTesting
    protected Transaction() {
    }

    /**
     * Get the list of configured extensions.
     * 
     * @return All extensions.
     */
    public List<FutureExtensions> getExtensions() {
        if (extensions == null) {
            extensions = new ArrayList<>();
        }
        return extensions;
    }

    /**
     * Get all Operations that have been added to this transaction.
     * 
     * @return All operations.
     */
    public List<Operation> getOperations() {
        return operations;
    }

    /**
     * Get the ref block number in its int representation.
     * 
     * The ref_block_num indicates a particular block in the past by referring
     * to the block number which has this number as the last two bytes.
     * 
     * @return The ref block number.
     */
    public UShort getRefBlockNum() {
        return refBlockNum;
    }

    /**
     * Get the ref block prefix in its long representation.
     * 
     * The ref_block_prefix on the other hand is obtain from the block id of
     * that particular reference block.
     * 
     * @return The ref block prefix.
     */
    public UInteger getRefBlockPrefix() {
        return refBlockPrefix;
    }

    /**
     * Extensions are currently not supported and will be ignored.
     * 
     * @param extensions
     *            Define a list of extensions.
     */
    public void setExtensions(List<FutureExtensions> extensions) {
        this.extensions = extensions;
    }

    /**
     * Define a list of operations that should be send with this transaction.
     * 
     * @param operations
     *            A list of operations.
     * @throws InvalidParameterException
     *             If the given object does not contain at least one Operation.
     */
    public void setOperations(List<Operation> operations) {
        if (operations == null || operations.isEmpty()) {
            throw new InvalidParameterException("At least one Operation is required.");
        }

        this.operations = operations;
    }

    /**
     * Set the ref block number by providing its int representation.
     * 
     * The ref_block_num indicates a particular block in the past by referring
     * to the block number which has this number as the last two bytes.
     * 
     * @param refBlockNum
     *            The ref block number as int.
     */
    public void setRefBlockNum(UShort refBlockNum) {
        this.refBlockNum = refBlockNum;
    }

    /**
     * Set the ref block prefix by providing its long representation. If you
     * only have the String representation use {@link #setRefBlockPrefix(String)
     * setRefBlockPrefix(String)}.
     * 
     * @param refBlockPrefix
     *            The ref block prefix.
     */
    public void setRefBlockPrefix(UInteger refBlockPrefix) {
        this.refBlockPrefix = refBlockPrefix;
    }

    /**
     * Set the ref block prefix by providing its String representation. The
     * String representation can be received from the @link
     * {@link eu.bittrade.libs.steemj.SteemJ#getDynamicGlobalProperties
     * getDynamicGlobalProperties} method.
     * 
     * @param refBlockPrefix
     *            The String representation of the ref block prefix.
     */
    public void setRefBlockPrefix(String refBlockPrefix) {
        this.refBlockPrefix = UInteger.valueOf(CryptoUtils.readUint32(CryptoUtils.HEX.decode(refBlockPrefix), 4));
    }

    /**
     * Get the currently configured expiration date. The expiration date defines
     * in which time the operation has to be processed. If not processed in the
     * given time, the transaction will not be accepted. <b>Notice</b> that this
     * method will return the latest possible expiration date if no other time
     * has been configured using the {@link #setExpirationDate(TimePointSec)
     * setExpirationDate(TimePointSec)} method.
     * 
     * @return The expiration date.
     */
    public TimePointSec getExpirationDate() {
        if (this.expirationDate == null || this.expirationDate.getDateTimeAsTimestamp() == 0) {
            // The expiration date is not set by the user so we do it on our own
            // by adding the maximal allowed offset to the current time.
            LOGGER.debug("No expiration date has been provided so the latest possible time is used.");
            return new TimePointSec(System.currentTimeMillis()
                    + SteemJConfig.getInstance().getMaximumExpirationDateOffset() - 60000L);
        }

        return this.expirationDate;
    }

    /**
     * Define how long this transaction is valid. If not processed in the given
     * time, the transaction will not be accepted.
     * 
     * @param expirationDate
     *            The expiration date to set.
     */
    public void setExpirationDate(TimePointSec expirationDate) {
        this.expirationDate = expirationDate;
    }

    /**
     * This method collects the required authorities for all operations stored
     * in this transaction. The returned list is already a minimized version to
     * avoid an "irrelevant signature included Unnecessary signature(s)
     * detected" error.
     * 
     * @return All required authorities and private key types.
     */
    protected Map<SignatureObject, PrivateKeyType> getRequiredAuthorities() {

        Map<SignatureObject, PrivateKeyType> requiredAuthorities = new HashMap<>();

        // Iterate over all Operations and collect the requried authorities.
        for (Operation operation : this.getOperations()) {
            requiredAuthorities.putAll(operation.getRequiredAuthorities(requiredAuthorities));
        }

        return requiredAuthorities;
    }

    /**
     * Validate if all fields of this transaction object have acceptable values.
     * 
     * @throws SteemInvalidTransactionException
     *             In case a field does not fulfill the requirements.
     */
    public void validate() throws SteemInvalidTransactionException {
        if (this.getExpirationDate()
                .getDateTimeAsTimestamp() > (new Timestamp(System.currentTimeMillis())).getTime()
                        + SteemJConfig.getInstance().getMaximumExpirationDateOffset()) {
            LOGGER.warn("The configured expiration date for this transaction is to far "
                    + "in the future and may not be accepted by the Steem node.");
        } else if (this.getExpirationDate().getDateTimeAsTimestamp() < (new Timestamp(System.currentTimeMillis()))
                .getTime()) {
            throw new SteemInvalidTransactionException("The expiration date can't be in the past.");
        }

        boolean isPostingKeyRequired = false;
        boolean isActiveKeyRequired = false;
        boolean isOwnerKeyRequired = false;

        // Posting authority cannot be mixed with active authority in same
        // transaction
        for (Entry<SignatureObject, PrivateKeyType> requiredAuthorities : getRequiredAuthorities().entrySet()) {
            PrivateKeyType keyType = requiredAuthorities.getValue();
            if (keyType.equals(PrivateKeyType.POSTING) && !isActiveKeyRequired && !isOwnerKeyRequired) {
                isPostingKeyRequired = true;
            } else if (keyType.equals(PrivateKeyType.ACTIVE) && !isPostingKeyRequired) {
                isActiveKeyRequired = true;
            } else if (keyType.equals(PrivateKeyType.OWNER) && !isPostingKeyRequired) {
                isOwnerKeyRequired = true;
            } else {
                throw new SteemInvalidTransactionException(
                        "Steem does not allow to process Operation requiring a POSTING "
                                + "key together with Operations requireing an ACTIVE or OWNER key.");
            }
        }
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
}