/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.core;
import static org.apache.commons.lang3.ArrayUtils.isEmpty;
import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY;
import static org.ethereum.util.ByteUtil.ZERO_BYTE_ARRAY;
import java.math.BigInteger;
import java.security.SignatureException;
import java.util.Arrays;
import org.ethereum.config.BlockchainNetConfig;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.ECKey.ECDSASignature;
import org.ethereum.crypto.ECKey.MissingPrivateKeyException;
import org.ethereum.crypto.HashUtil;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.RLP;
import org.ethereum.util.RLPElement;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.BigIntegers;
import org.spongycastle.util.encoders.Hex;
/**
* A transaction (formally, T) is a single cryptographically
* signed instruction sent by an actor external to Ethereum.
* An external actor can be a person (via a mobile device or desktop computer)
* or could be from a piece of automated software running on a server.
* There are two types of transactions: those which result in message calls
* and those which result in the creation of new contracts.
*/
public class Transaction {
private static final Logger logger = LoggerFactory.getLogger(Transaction.class);
private static final BigInteger DEFAULT_GAS_PRICE = new BigInteger("10000000000000");
private static final BigInteger DEFAULT_BALANCE_GAS = new BigInteger("21000");
public static final int HASH_LENGTH = 32;
public static final int ADDRESS_LENGTH = 20;
/* SHA3 hash of the RLP encoded transaction */
private byte[] hash;
/* a counter used to make sure each transaction can only be processed once */
private byte[] nonce;
/* the amount of ether to transfer (calculated as wei) */
private byte[] value;
/* the address of the destination account
* In creation transaction the receive address is - 0 */
private byte[] receiveAddress;
/* the amount of ether to pay as a transaction fee
* to the miner for each unit of gas */
private byte[] gasPrice;
/* the amount of "gas" to allow for the computation.
* Gas is the fuel of the computational engine;
* every computational step taken and every byte added
* to the state or transaction list consumes some gas. */
private byte[] gasLimit;
/* An unlimited size byte array specifying
* input [data] of the message call or
* Initialization code for a new contract */
private byte[] data;
/**
* Since EIP-155, we could encode chainId in V
*/
private static final int CHAIN_ID_INC = 35;
private static final int LOWER_REAL_V = 27;
private Integer chainId = null;
/* the elliptic curve signature
* (including public key recovery bits) */
private ECDSASignature signature;
protected byte[] sendAddress;
/* Tx in encoded form */
protected byte[] rlpEncoded;
private byte[] rlpRaw;
/* Indicates if this transaction has been parsed
* from the RLP-encoded data */
protected boolean parsed = false;
public Transaction(byte[] rawData) {
this.rlpEncoded = rawData;
parsed = false;
}
public Transaction(byte[] nonce, byte[] gasPrice, byte[] gasLimit, byte[] receiveAddress, byte[] value, byte[] data,
Integer chainId) {
this.nonce = nonce;
this.gasPrice = gasPrice;
this.gasLimit = gasLimit;
this.receiveAddress = receiveAddress;
if (ByteUtil.isSingleZero(value)) {
this.value = EMPTY_BYTE_ARRAY;
} else {
this.value = value;
}
this.data = data;
this.chainId = chainId;
if (receiveAddress == null) {
this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY;
}
parsed = true;
}
/**
* Warning: this transaction would not be protected by replay-attack protection mechanism
* Use {@link Transaction#Transaction(byte[], byte[], byte[], byte[], byte[], byte[], Integer)} constructor instead
* and specify the desired chainID
*/
public Transaction(byte[] nonce, byte[] gasPrice, byte[] gasLimit, byte[] receiveAddress, byte[] value, byte[] data) {
this(nonce, gasPrice, gasLimit, receiveAddress, value, data, null);
}
public Transaction(byte[] nonce, byte[] gasPrice, byte[] gasLimit, byte[] receiveAddress, byte[] value, byte[] data,
byte[] r, byte[] s, byte v, Integer chainId) {
this(nonce, gasPrice, gasLimit, receiveAddress, value, data, chainId);
this.signature = ECDSASignature.fromComponents(r, s, v);
}
/**
* Warning: this transaction would not be protected by replay-attack protection mechanism
* Use {@link Transaction#Transaction(byte[], byte[], byte[], byte[], byte[], byte[], byte[], byte[], byte, Integer)}
* constructor instead and specify the desired chainID
*/
public Transaction(byte[] nonce, byte[] gasPrice, byte[] gasLimit, byte[] receiveAddress, byte[] value, byte[] data,
byte[] r, byte[] s, byte v) {
this(nonce, gasPrice, gasLimit, receiveAddress, value, data, r, s, v, null);
}
private Integer extractChainIdFromV(BigInteger bv) {
if (bv.bitLength() > 31) return Integer.MAX_VALUE; // chainId is limited to 31 bits, longer are not valid for now
long v = bv.longValue();
if (v == LOWER_REAL_V || v == (LOWER_REAL_V + 1)) return null;
return (int) ((v - CHAIN_ID_INC) / 2);
}
private byte getRealV(BigInteger bv) {
if (bv.bitLength() > 31) return 0; // chainId is limited to 31 bits, longer are not valid for now
long v = bv.longValue();
if (v == LOWER_REAL_V || v == (LOWER_REAL_V + 1)) return (byte) v;
byte realV = LOWER_REAL_V;
int inc = 0;
if ((int) v % 2 == 0) inc = 1;
return (byte) (realV + inc);
}
public long transactionCost(BlockchainNetConfig config, Block block){
rlpParse();
return config.getConfigForBlock(block.getNumber()).
getTransactionCost(this);
}
public synchronized void verify() {
rlpParse();
validate();
}
public synchronized void rlpParse() {
if (parsed) return;
try {
RLPList decodedTxList = RLP.decode2(rlpEncoded);
RLPList transaction = (RLPList) decodedTxList.get(0);
// Basic verification
if (transaction.size() > 9 ) throw new RuntimeException("Too many RLP elements");
for (RLPElement rlpElement : transaction) {
if (!(rlpElement instanceof RLPItem))
throw new RuntimeException("Transaction RLP elements shouldn't be lists");
}
this.nonce = transaction.get(0).getRLPData();
this.gasPrice = transaction.get(1).getRLPData();
this.gasLimit = transaction.get(2).getRLPData();
this.receiveAddress = transaction.get(3).getRLPData();
this.value = transaction.get(4).getRLPData();
this.data = transaction.get(5).getRLPData();
// only parse signature in case tx is signed
if (transaction.get(6).getRLPData() != null) {
byte[] vData = transaction.get(6).getRLPData();
BigInteger v = ByteUtil.bytesToBigInteger(vData);
this.chainId = extractChainIdFromV(v);
byte[] r = transaction.get(7).getRLPData();
byte[] s = transaction.get(8).getRLPData();
this.signature = ECDSASignature.fromComponents(r, s, getRealV(v));
} else {
logger.debug("RLP encoded tx is not signed!");
}
this.parsed = true;
this.hash = getHash();
} catch (Exception e) {
throw new RuntimeException("Error on parsing RLP", e);
}
}
private void validate() {
if (getNonce().length > HASH_LENGTH) throw new RuntimeException("Nonce is not valid");
if (receiveAddress != null && receiveAddress.length != 0 && receiveAddress.length != ADDRESS_LENGTH)
throw new RuntimeException("Receive address is not valid");
if (gasLimit.length > HASH_LENGTH)
throw new RuntimeException("Gas Limit is not valid");
if (gasPrice != null && gasPrice.length > HASH_LENGTH)
throw new RuntimeException("Gas Price is not valid");
if (value != null && value.length > HASH_LENGTH)
throw new RuntimeException("Value is not valid");
if (getSignature() != null) {
if (BigIntegers.asUnsignedByteArray(signature.r).length > HASH_LENGTH)
throw new RuntimeException("Signature R is not valid");
if (BigIntegers.asUnsignedByteArray(signature.s).length > HASH_LENGTH)
throw new RuntimeException("Signature S is not valid");
if (getSender() != null && getSender().length != ADDRESS_LENGTH)
throw new RuntimeException("Sender is not valid");
}
}
public boolean isParsed() {
return parsed;
}
public byte[] getHash() {
if (!isEmpty(hash)) return hash;
rlpParse();
byte[] plainMsg = this.getEncoded();
return HashUtil.sha3(plainMsg);
}
public byte[] getRawHash() {
rlpParse();
byte[] plainMsg = this.getEncodedRaw();
return HashUtil.sha3(plainMsg);
}
public byte[] getNonce() {
rlpParse();
return nonce == null ? ZERO_BYTE_ARRAY : nonce;
}
protected void setNonce(byte[] nonce) {
this.nonce = nonce;
parsed = true;
}
public boolean isValueTx() {
rlpParse();
return value != null;
}
public byte[] getValue() {
rlpParse();
return value == null ? ZERO_BYTE_ARRAY : value;
}
protected void setValue(byte[] value) {
this.value = value;
parsed = true;
}
public byte[] getReceiveAddress() {
rlpParse();
return receiveAddress;
}
protected void setReceiveAddress(byte[] receiveAddress) {
this.receiveAddress = receiveAddress;
parsed = true;
}
public byte[] getGasPrice() {
rlpParse();
return gasPrice == null ? ZERO_BYTE_ARRAY : gasPrice;
}
protected void setGasPrice(byte[] gasPrice) {
this.gasPrice = gasPrice;
parsed = true;
}
public byte[] getGasLimit() {
rlpParse();
return gasLimit == null ? ZERO_BYTE_ARRAY : gasLimit;
}
protected void setGasLimit(byte[] gasLimit) {
this.gasLimit = gasLimit;
parsed = true;
}
public long nonZeroDataBytes() {
if (data == null) return 0;
int counter = 0;
for (final byte aData : data) {
if (aData != 0) ++counter;
}
return counter;
}
public long zeroDataBytes() {
if (data == null) return 0;
int counter = 0;
for (final byte aData : data) {
if (aData == 0) ++counter;
}
return counter;
}
public byte[] getData() {
rlpParse();
return data;
}
protected void setData(byte[] data) {
this.data = data;
parsed = true;
}
public ECDSASignature getSignature() {
rlpParse();
return signature;
}
public byte[] getContractAddress() {
if (!isContractCreation()) return null;
return HashUtil.calcNewAddr(this.getSender(), this.getNonce());
}
public boolean isContractCreation() {
rlpParse();
return this.receiveAddress == null || Arrays.equals(this.receiveAddress,ByteUtil.EMPTY_BYTE_ARRAY);
}
/*
* Crypto
*/
public ECKey getKey() {
byte[] hash = getRawHash();
return ECKey.recoverFromSignature(signature.v, signature, hash);
}
public synchronized byte[] getSender() {
try {
if (sendAddress == null) {
sendAddress = ECKey.signatureToAddress(getRawHash(), getSignature());
}
return sendAddress;
} catch (SignatureException e) {
logger.error(e.getMessage(), e);
}
return null;
}
public Integer getChainId() {
rlpParse();
return chainId == null ? null : (int) chainId;
}
/**
* @deprecated should prefer #sign(ECKey) over this method
*/
public void sign(byte[] privKeyBytes) throws MissingPrivateKeyException {
sign(ECKey.fromPrivate(privKeyBytes));
}
public void sign(ECKey key) throws MissingPrivateKeyException {
this.signature = key.sign(this.getRawHash());
this.rlpEncoded = null;
}
@Override
public String toString() {
return toString(Integer.MAX_VALUE);
}
public String toString(int maxDataSize) {
rlpParse();
String dataS;
if (data == null) {
dataS = "";
} else if (data.length < maxDataSize) {
dataS = ByteUtil.toHexString(data);
} else {
dataS = ByteUtil.toHexString(Arrays.copyOfRange(data, 0, maxDataSize)) +
"... (" + data.length + " bytes)";
}
return "TransactionData [" + "hash=" + ByteUtil.toHexString(hash) +
" nonce=" + ByteUtil.toHexString(nonce) +
", gasPrice=" + ByteUtil.toHexString(gasPrice) +
", gas=" + ByteUtil.toHexString(gasLimit) +
", receiveAddress=" + ByteUtil.toHexString(receiveAddress) +
", sendAddress=" + ByteUtil.toHexString(getSender()) +
", value=" + ByteUtil.toHexString(value) +
", data=" + dataS +
", signatureV=" + (signature == null ? "" : signature.v) +
", signatureR=" + (signature == null ? "" : ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.r))) +
", signatureS=" + (signature == null ? "" : ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.s))) +
"]";
}
/**
* For signatures you have to keep also
* RLP of the transaction without any signature data
*/
public byte[] getEncodedRaw() {
rlpParse();
if (rlpRaw != null) return rlpRaw;
// parse null as 0 for nonce
byte[] nonce = null;
if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) {
nonce = RLP.encodeElement(null);
} else {
nonce = RLP.encodeElement(this.nonce);
}
byte[] gasPrice = RLP.encodeElement(this.gasPrice);
byte[] gasLimit = RLP.encodeElement(this.gasLimit);
byte[] receiveAddress = RLP.encodeElement(this.receiveAddress);
byte[] value = RLP.encodeElement(this.value);
byte[] data = RLP.encodeElement(this.data);
// Since EIP-155 use chainId for v
if (chainId == null) {
rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress,
value, data);
} else {
byte[] v, r, s;
v = RLP.encodeInt(chainId);
r = RLP.encodeElement(EMPTY_BYTE_ARRAY);
s = RLP.encodeElement(EMPTY_BYTE_ARRAY);
rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress,
value, data, v, r, s);
}
return rlpRaw;
}
public byte[] getEncoded() {
if (rlpEncoded != null) return rlpEncoded;
// parse null as 0 for nonce
byte[] nonce = null;
if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) {
nonce = RLP.encodeElement(null);
} else {
nonce = RLP.encodeElement(this.nonce);
}
byte[] gasPrice = RLP.encodeElement(this.gasPrice);
byte[] gasLimit = RLP.encodeElement(this.gasLimit);
byte[] receiveAddress = RLP.encodeElement(this.receiveAddress);
byte[] value = RLP.encodeElement(this.value);
byte[] data = RLP.encodeElement(this.data);
byte[] v, r, s;
if (signature != null) {
int encodeV;
if (chainId == null) {
encodeV = signature.v;
} else {
encodeV = signature.v - LOWER_REAL_V;
encodeV += chainId * 2 + CHAIN_ID_INC;
}
v = RLP.encodeInt(encodeV);
r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r));
s = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.s));
} else {
// Since EIP-155 use chainId for v
v = chainId == null ? RLP.encodeElement(EMPTY_BYTE_ARRAY) : RLP.encodeInt(chainId);
r = RLP.encodeElement(EMPTY_BYTE_ARRAY);
s = RLP.encodeElement(EMPTY_BYTE_ARRAY);
}
this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit,
receiveAddress, value, data, v, r, s);
this.hash = this.getHash();
return rlpEncoded;
}
@Override
public int hashCode() {
byte[] hash = this.getHash();
int hashCode = 0;
for (int i = 0; i < hash.length; ++i) {
hashCode += hash[i] * i;
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Transaction)) return false;
Transaction tx = (Transaction) obj;
return tx.hashCode() == this.hashCode();
}
/**
* @deprecated Use {@link Transaction#createDefault(String, BigInteger, BigInteger, Integer)} instead
*/
public static Transaction createDefault(String to, BigInteger amount, BigInteger nonce){
return create(to, amount, nonce, DEFAULT_GAS_PRICE, DEFAULT_BALANCE_GAS);
}
public static Transaction createDefault(String to, BigInteger amount, BigInteger nonce, Integer chainId){
return create(to, amount, nonce, DEFAULT_GAS_PRICE, DEFAULT_BALANCE_GAS, chainId);
}
/**
* @deprecated use {@link Transaction#create(String, BigInteger, BigInteger, BigInteger, BigInteger, Integer)} instead
*/
public static Transaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit){
return new Transaction(BigIntegers.asUnsignedByteArray(nonce),
BigIntegers.asUnsignedByteArray(gasPrice),
BigIntegers.asUnsignedByteArray(gasLimit),
Hex.decode(to),
BigIntegers.asUnsignedByteArray(amount),
null);
}
public static Transaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice,
BigInteger gasLimit, Integer chainId){
return new Transaction(BigIntegers.asUnsignedByteArray(nonce),
BigIntegers.asUnsignedByteArray(gasPrice),
BigIntegers.asUnsignedByteArray(gasLimit),
Hex.decode(to),
BigIntegers.asUnsignedByteArray(amount),
null,
chainId);
}
}