package qora.transaction;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
import org.json.simple.JSONObject;
import qora.account.Account;
import qora.crypto.Base58;
import qora.crypto.Crypto;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import database.DBSet;
public class GenesisTransaction extends Transaction {
private static final int RECIPIENT_LENGTH = Account.ADDRESS_LENGTH;
private static final int AMOUNT_LENGTH = 8;
private static final int BASE_LENGTH = TIMESTAMP_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
private Account recipient;
private BigDecimal amount;
public GenesisTransaction(Account recipient, BigDecimal amount, long timestamp)
{
super(GENESIS_TRANSACTION, BigDecimal.ZERO, timestamp, new byte[]{}, generateSignature(recipient, amount, timestamp));
this.recipient = recipient;
this.amount = amount;
}
//GETTERS/SETTERS
@Override
public byte[] getSignature() {
return generateSignature(this.recipient, this.amount, this.timestamp);
}
public Account getRecipient()
{
return this.recipient;
}
public BigDecimal getAmount()
{
return this.amount;
}
//PARSE/CONVERT
public static Transaction Parse(byte[] data) throws Exception{
//CHECK IF WE MATCH BLOCK LENGTH
if(data.length < BASE_LENGTH)
{
throw new Exception("Data does not match block length");
}
int position = 0;
//READ TIMESTAMP
byte[] timestampBytes = Arrays.copyOfRange(data, position, position + TIMESTAMP_LENGTH);
long timestamp = Longs.fromByteArray(timestampBytes);
position += TIMESTAMP_LENGTH;
//READ RECIPIENT
byte[] recipientBytes = Arrays.copyOfRange(data, position, position + RECIPIENT_LENGTH);
Account recipient = new Account(Base58.encode(recipientBytes));
position += RECIPIENT_LENGTH;
//READ AMOUNT
byte[] amountBytes = Arrays.copyOfRange(data, position, position + AMOUNT_LENGTH);
BigDecimal amount = new BigDecimal(new BigInteger(amountBytes), 8);
return new GenesisTransaction(recipient, amount, timestamp);
}
@SuppressWarnings("unchecked")
@Override
public JSONObject toJson() {
//GET BASE
JSONObject transaction = this.getJsonBase();
//ADD RECIPIENT/AMOUNT
transaction.put("recipient", this.recipient.getAddress());
transaction.put("amount", this.amount.toPlainString());
return transaction;
}
@Override
public byte[] toBytes()
{
byte[] data = new byte[0];
//WRITE TYPE
byte[] typeBytes = Ints.toByteArray(GENESIS_TRANSACTION);
typeBytes = Bytes.ensureCapacity(typeBytes, TYPE_LENGTH, 0);
data = Bytes.concat(data, typeBytes);
//WRITE TIMESTAMP
byte[] timestampBytes = Longs.toByteArray(this.timestamp);
timestampBytes = Bytes.ensureCapacity(timestampBytes, TIMESTAMP_LENGTH, 0);
data = Bytes.concat(data, timestampBytes);
//WRITE RECIPIENT
data = Bytes.concat(data, Base58.decode(this.recipient.getAddress()));
//WRITE AMOUNT
byte[] amountBytes = this.amount.unscaledValue().toByteArray();
byte[] fill = new byte[AMOUNT_LENGTH - amountBytes.length];
amountBytes = Bytes.concat(fill, amountBytes);
data = Bytes.concat(data, amountBytes);
return data;
}
@Override
public int getDataLength()
{
return TYPE_LENGTH + BASE_LENGTH;
}
//VALIDATE
public boolean isSignatureValid()
{
byte[] data = new byte[0];
//WRITE TYPE
byte[] typeBytes = Ints.toByteArray(GENESIS_TRANSACTION);
typeBytes = Bytes.ensureCapacity(typeBytes, TYPE_LENGTH, 0);
data = Bytes.concat(data, typeBytes);
//WRITE TIMESTAMP
byte[] timestampBytes = Longs.toByteArray(this.timestamp);
timestampBytes = Bytes.ensureCapacity(timestampBytes, TIMESTAMP_LENGTH, 0);
data = Bytes.concat(data, timestampBytes);
//WRITE RECIPIENT
data = Bytes.concat(data, Base58.decode(this.recipient.getAddress()));
//WRITE AMOUNT
byte[] amountBytes = this.amount.unscaledValue().toByteArray();
byte[] fill = new byte[AMOUNT_LENGTH - amountBytes.length];
amountBytes = Bytes.concat(fill, amountBytes);
data = Bytes.concat(data, amountBytes);
//DIGEST
byte[] digest = Crypto.getInstance().digest(data);
digest = Bytes.concat(digest, digest);
//CHECK IF EQUAL
return Arrays.equals(digest, this.signature);
}
@Override
public int isValid(DBSet db)
{
//CHECK IF AMOUNT IS POSITIVE
if(this.amount.compareTo(BigDecimal.ZERO) == -1)
{
return NEGATIVE_AMOUNT;
}
//CHECK IF ADDRESS IS VALID
if(!Crypto.getInstance().isValidAddress(this.recipient.getAddress()))
{
return INVALID_ADDRESS;
}
return VALIDATE_OKE;
}
//PROCESS/ORPHAN
@Override
public void process(DBSet db) {
//UPDATE BALANCE
this.recipient.setConfirmedBalance(this.amount, db);
//SET AS REFERENCE
recipient.setLastReference(this.signature, db);
}
@Override
public void orphan(DBSet db)
{
//UNDO BALANCE
this.recipient.setConfirmedBalance(BigDecimal.ZERO, db);
//UNDO REFERENCE
this.recipient.removeReference(db);
}
//REST
private static byte[] generateSignature(Account recipient, BigDecimal amount, long timestamp)
{
byte[] data = new byte[0];
//WRITE TYPE
byte[] typeBytes = Ints.toByteArray(GENESIS_TRANSACTION);
typeBytes = Bytes.ensureCapacity(typeBytes, TYPE_LENGTH, 0);
data = Bytes.concat(data, typeBytes);
//WRITE TIMESTAMP
byte[] timestampBytes = Longs.toByteArray(timestamp);
timestampBytes = Bytes.ensureCapacity(timestampBytes, TIMESTAMP_LENGTH, 0);
data = Bytes.concat(data, timestampBytes);
//WRITE RECIPIENT
data = Bytes.concat(data, Base58.decode(recipient.getAddress()));
//WRITE AMOUNT
byte[] amountBytes = amount.unscaledValue().toByteArray();
byte[] fill = new byte[AMOUNT_LENGTH - amountBytes.length];
amountBytes = Bytes.concat(fill, amountBytes);
data = Bytes.concat(data, amountBytes);
//DIGEST
byte[] digest = Crypto.getInstance().digest(data);
digest = Bytes.concat(digest, digest);
return digest;
}
@Override
public Account getCreator()
{
return null;
}
@Override
public List<Account> getInvolvedAccounts()
{
return Arrays.asList(this.recipient);
}
@Override
public boolean isInvolved(Account account)
{
return this.recipient.getAddress().equals(account.getAddress());
}
@Override
public BigDecimal getAmount(Account account)
{
if(this.recipient.getAddress().equals(account.getAddress()))
{
return this.amount;
}
return BigDecimal.ZERO;
}
}