diff --git a/src/main/java/io/horizen/lambo/car/transaction/AbstractRegularTransaction.java b/src/main/java/io/horizen/lambo/car/transaction/AbstractRegularTransaction.java deleted file mode 100644 index 412aa14..0000000 --- a/src/main/java/io/horizen/lambo/car/transaction/AbstractRegularTransaction.java +++ /dev/null @@ -1,117 +0,0 @@ -package io.horizen.lambo.car.transaction; - -import com.horizen.box.*; -import com.horizen.box.data.*; -import com.horizen.proof.Proof; -import com.horizen.proof.Signature25519; -import com.horizen.proof.Signature25519Serializer; -import com.horizen.proposition.Proposition; -import com.horizen.transaction.SidechainTransaction; -import com.horizen.utils.ListSerializer; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -// AbstractRegularTransaction is an abstract class that was designed to work with RegularBoxes only. -// This class can spent RegularBoxes and create new RegularBoxes. -// It also support fee payment logic. -public abstract class AbstractRegularTransaction extends SidechainTransaction> { - - protected final List inputRegularBoxIds; - protected final List inputRegularBoxProofs; - protected final List outputRegularBoxesData; - - protected final long fee; - protected final long timestamp; - - protected static ListSerializer regularBoxProofsSerializer = - new ListSerializer<>(Signature25519Serializer.getSerializer(), MAX_TRANSACTION_UNLOCKERS); - protected static ListSerializer regularBoxDataListSerializer = - new ListSerializer<>(RegularBoxDataSerializer.getSerializer(), MAX_TRANSACTION_NEW_BOXES); - - private List> newBoxes; - - public AbstractRegularTransaction(List inputRegularBoxIds, // regular box ids to spent - List inputRegularBoxProofs, // proofs to spent regular boxes - List outputRegularBoxesData, // destinations where to send regular coins - long fee, // fee to be paid - long timestamp) { // creation time in milliseconds from epoch - // Number of input ids should be equal to number of proofs, otherwise transaction is for sure invalid. - if(inputRegularBoxIds.size() != inputRegularBoxProofs.size()) - throw new IllegalArgumentException("Regular box inputs list size is different to proving signatures list size!"); - - this.inputRegularBoxIds = inputRegularBoxIds; - this.inputRegularBoxProofs = inputRegularBoxProofs; - this.outputRegularBoxesData = outputRegularBoxesData; - this.fee = fee; - this.timestamp = timestamp; - } - - - // Box ids to open and proofs is expected to be aggregated together and represented as Unlockers. - // Important: all boxes which must be opened as a part of the Transaction MUST be represented as Unlocker. - @Override - public List> unlockers() { - // All the transactions expected to be immutable, so we keep this list cached to avoid redundant calculations. - List> unlockers = new ArrayList<>(); - // Fill the list with the regular inputs. - for (int i = 0; i < inputRegularBoxIds.size() && i < inputRegularBoxProofs.size(); i++) { - int finalI = i; - BoxUnlocker unlocker = new BoxUnlocker() { - @Override - public byte[] closedBoxId() { - return inputRegularBoxIds.get(finalI); - } - - @Override - public Proof boxKey() { - return inputRegularBoxProofs.get(finalI); - } - }; - unlockers.add(unlocker); - } - - return unlockers; - } - - // Specify the output boxes. - // Nonce calculation algorithm is deterministic. So it's forbidden to set nonce in different way. - // The check for proper nonce is defined in SidechainTransaction.semanticValidity method. - // Such an algorithm is needed to disallow box ids manipulation and different vulnerabilities related to this. - @Override - public List> newBoxes() { - if(newBoxes == null) { - newBoxes = new ArrayList<>(); - for (int i = 0; i < outputRegularBoxesData.size(); i++) { - long nonce = getNewBoxNonce(outputRegularBoxesData.get(i).proposition(), i); - RegularBoxData boxData = outputRegularBoxesData.get(i); - newBoxes.add((NoncedBox) new RegularBox(boxData, nonce)); - } - } - return Collections.unmodifiableList(newBoxes); - } - - @Override - public long fee() { - return fee; - } - - @Override - public long timestamp() { - return timestamp; - } - - @Override - public boolean transactionSemanticValidity() { - if(fee < 0 || timestamp < 0) - return false; - - // check that we have enough proofs. - if(inputRegularBoxIds.size() != inputRegularBoxProofs.size()) { - return false; - } - - return true; - } -} diff --git a/src/main/java/io/horizen/lambo/car/transaction/BuyCarTransaction.java b/src/main/java/io/horizen/lambo/car/transaction/BuyCarTransaction.java index b0e072e..b3e1508 100644 --- a/src/main/java/io/horizen/lambo/car/transaction/BuyCarTransaction.java +++ b/src/main/java/io/horizen/lambo/car/transaction/BuyCarTransaction.java @@ -1,180 +1,171 @@ -package io.horizen.lambo.car.transaction; - -import com.google.common.primitives.Bytes; -import com.google.common.primitives.Ints; -import com.google.common.primitives.Longs; -import com.horizen.box.BoxUnlocker; -import com.horizen.box.NoncedBox; -import com.horizen.box.RegularBox; -import com.horizen.box.data.RegularBoxData; -import io.horizen.lambo.car.box.CarBox; -import io.horizen.lambo.car.info.CarBuyOrderInfo; -import com.horizen.proof.Proof; -import com.horizen.proof.Signature25519; -import com.horizen.proposition.Proposition; -import com.horizen.transaction.TransactionSerializer; -import com.horizen.utils.BytesUtils; -import scorex.core.NodeViewModifier$; - -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static io.horizen.lambo.car.transaction.CarRegistryTransactionsIdsEnum.BuyCarTransactionId; - -// BuyCarTransaction is nested from AbstractRegularTransaction so support regular coins transmission as well. -// BuyCarTransaction was designed to accept the SellOrder by specific buyer or to cancel it by the owner. -// As outputs it contains possible RegularBoxes(to pay to the sell order owner and make change) and new CarBox entry. -// As unlockers it contains RegularBoxes and CarSellOrder to open. -public final class BuyCarTransaction extends AbstractRegularTransaction { - - // CarBuyOrderInfo is a view that describes what sell order to open and who will be the next owner. - // But inside it contains just a minimum set of info (like CarSellOrderBox itself and proof) that is the unique source of data. - // So, no one outside controls what will be the specific outputs of this transaction. - // Any malicious actions will lead to transaction invalidation. - // For example, if SellOrder was accepted by the buyer specified, CarBuyOrderInfo view returns as the new box data - // new instance of CarBoxData the owned by the buyer and RegularBoxData with the payment to previous owner. - private final CarBuyOrderInfo carBuyOrderInfo; - - private List> newBoxes; - - public BuyCarTransaction(List inputRegularBoxIds, - List inputRegularBoxProofs, - List outputRegularBoxesData, - CarBuyOrderInfo carBuyOrderInfo, - long fee, - long timestamp) { - super(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, fee, timestamp); - this.carBuyOrderInfo = carBuyOrderInfo; - } - - // Specify the unique custom transaction id. - @Override - public byte transactionTypeId() { - return BuyCarTransactionId.id(); - } - - // Override unlockers to contains regularBoxes from the parent class appended with CarSellOrderBox entry. - @Override - public List> unlockers() { - // Get Regular unlockers from base class. - List> unlockers = super.unlockers(); - - BoxUnlocker unlocker = new BoxUnlocker() { - @Override - public byte[] closedBoxId() { - return carBuyOrderInfo.getCarSellOrderBoxToOpen().id(); - } - - @Override - public Proof boxKey() { - return carBuyOrderInfo.getCarSellOrderSpendingProof(); - } - }; - // Append with the CarSellOrderBox unlocker entry. - unlockers.add(unlocker); - - return unlockers; - } - - // Override newBoxes to contains regularBoxes from the parent class appended with CarBox and payment entries. - // The nonce calculation algorithm for Boxes is the same as in parent class. - @Override - public List> newBoxes() { - if(newBoxes == null) { - // Get new boxes from base class. - newBoxes = new ArrayList<>(super.newBoxes()); - - // Set CarBox with specific owner depends on proof. See CarBuyOrderInfo.getNewOwnerCarBoxData() definition. - long nonce = getNewBoxNonce(carBuyOrderInfo.getNewOwnerCarBoxData().proposition(), newBoxes.size()); - newBoxes.add((NoncedBox) new CarBox(carBuyOrderInfo.getNewOwnerCarBoxData(), nonce)); - - // If Sell Order was opened by the buyer -> add payment box for Car previous owner. - if (!carBuyOrderInfo.isSpentByOwner()) { - RegularBoxData paymentBoxData = carBuyOrderInfo.getPaymentBoxData(); - nonce = getNewBoxNonce(paymentBoxData.proposition(), newBoxes.size()); - newBoxes.add((NoncedBox) new RegularBox(paymentBoxData, nonce)); - } - } - return Collections.unmodifiableList(newBoxes); - - } - - // Define object serialization, that should serialize both parent class entries and CarBuyOrderInfo as well - @Override - public byte[] bytes() { - ByteArrayOutputStream inputsIdsStream = new ByteArrayOutputStream(); - for(byte[] id: inputRegularBoxIds) - inputsIdsStream.write(id, 0, id.length); - - byte[] inputRegularBoxIdsBytes = inputsIdsStream.toByteArray(); - - byte[] inputRegularBoxProofsBytes = regularBoxProofsSerializer.toBytes(inputRegularBoxProofs); - - byte[] outputRegularBoxesDataBytes = regularBoxDataListSerializer.toBytes(outputRegularBoxesData); - - byte[] carBuyOrderInfoBytes = carBuyOrderInfo.bytes(); - - return Bytes.concat( - Longs.toByteArray(fee()), // 8 bytes - Longs.toByteArray(timestamp()), // 8 bytes - Ints.toByteArray(inputRegularBoxIdsBytes.length), // 4 bytes - inputRegularBoxIdsBytes, // depends on previous value (>=4 bytes) - Ints.toByteArray(inputRegularBoxProofsBytes.length), // 4 bytes - inputRegularBoxProofsBytes, // depends on previous value (>=4 bytes) - Ints.toByteArray(outputRegularBoxesDataBytes.length), // 4 bytes - outputRegularBoxesDataBytes, // depends on previous value (>=4 bytes) - Ints.toByteArray(carBuyOrderInfoBytes.length), // 4 bytes - carBuyOrderInfoBytes // depends on previous value (>=4 bytes) - ); - } - - // Define object deserialization similar to 'toBytes()' representation. - public static BuyCarTransaction parseBytes(byte[] bytes) { - int offset = 0; - - long fee = BytesUtils.getLong(bytes, offset); - offset += 8; - - long timestamp = BytesUtils.getLong(bytes, offset); - offset += 8; - - int batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - ArrayList inputRegularBoxIds = new ArrayList<>(); - int idLength = NodeViewModifier$.MODULE$.ModifierIdSize(); - while(batchSize > 0) { - inputRegularBoxIds.add(Arrays.copyOfRange(bytes, offset, offset + idLength)); - offset += idLength; - batchSize -= idLength; - } - - batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - List inputRegularBoxProofs = regularBoxProofsSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); - offset += batchSize; - - batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - List outputRegularBoxesData = regularBoxDataListSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); - offset += batchSize; - - batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - CarBuyOrderInfo carBuyOrderInfo = CarBuyOrderInfo.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); - - return new BuyCarTransaction(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, carBuyOrderInfo, fee, timestamp); - } - - // Set specific Serializer for BuyCarTransaction class. - @Override - public TransactionSerializer serializer() { - return BuyCarTransactionSerializer.getSerializer(); - } -} +package io.horizen.lambo.car.transaction; + +import com.google.common.primitives.Bytes; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.horizen.box.BoxUnlocker; +import com.horizen.box.NoncedBox; +import com.horizen.box.data.NoncedBoxData; +import com.horizen.box.data.RegularBoxData; +import io.horizen.lambo.car.info.CarBuyOrderInfo; +import com.horizen.proof.Proof; +import com.horizen.proof.Signature25519; +import com.horizen.proposition.Proposition; +import com.horizen.transaction.TransactionSerializer; +import com.horizen.transaction.AbstractRegularTransaction; +import com.horizen.utils.BytesUtils; +import scorex.core.NodeViewModifier$; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static io.horizen.lambo.car.transaction.CarRegistryTransactionsIdsEnum.BuyCarTransactionId; + +// BuyCarTransaction is nested from AbstractRegularTransaction so support regular coins transmission as well. +// BuyCarTransaction was designed to accept the SellOrder by specific buyer or to cancel it by the owner. +// As outputs it contains possible RegularBoxes(to pay to the sell order owner and make change) and new CarBox entry. +// As unlockers it contains RegularBoxes and CarSellOrder to open. +public final class BuyCarTransaction extends AbstractRegularTransaction { + + // CarBuyOrderInfo is a view that describes what sell order to open and who will be the next owner. + // But inside it contains just a minimum set of info (like CarSellOrderBox itself and proof) that is the unique source of data. + // So, no one outside controls what will be the specific outputs of this transaction. + // Any malicious actions will lead to transaction invalidation. + // For example, if SellOrder was accepted by the buyer specified, CarBuyOrderInfo view returns as the new box data + // new instance of CarBoxData the owned by the buyer and RegularBoxData with the payment to previous owner. + private final CarBuyOrderInfo carBuyOrderInfo; + + public BuyCarTransaction(List inputRegularBoxIds, + List inputRegularBoxProofs, + List outputRegularBoxesData, + CarBuyOrderInfo carBuyOrderInfo, + long fee, + long timestamp) { + super(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, fee, timestamp); + + // Parameters sanity check + if(carBuyOrderInfo == null){ + throw new IllegalArgumentException("Unacceptable value of carBuyOrderInfo!"); + } + this.carBuyOrderInfo = carBuyOrderInfo; + } + + // Specify the unique custom transaction id. + @Override + public byte transactionTypeId() { + return BuyCarTransactionId.id(); + } + + // Gets CarBuyOrderInfo-related boxes data + @Override + protected List>> getCustomOutputData(){ + // Set CarBox with specific owner depends on proof. See CarBuyOrderInfo.getNewOwnerCarBoxData() definition. + List>> allBoxesData = Arrays.asList((NoncedBoxData) carBuyOrderInfo.getNewOwnerCarBoxData()); + // If Sell Order was opened by the buyer -> add payment box for Car previous owner. + if (!carBuyOrderInfo.isSpentByOwner()){ + allBoxesData.add((NoncedBoxData) carBuyOrderInfo.getPaymentBoxData()); + } + return allBoxesData; + } + + // Override unlockers to contains regularBoxes from the parent class appended with CarSellOrderBox entry. + @Override + public List> unlockers() { + // Get Regular unlockers from base class. + List> unlockers = super.unlockers(); + + BoxUnlocker unlocker = new BoxUnlocker() { + @Override + public byte[] closedBoxId() { + return carBuyOrderInfo.getCarSellOrderBoxToOpen().id(); + } + + @Override + public Proof boxKey() { + return carBuyOrderInfo.getCarSellOrderSpendingProof(); + } + }; + // Append with the CarSellOrderBox unlocker entry. + unlockers.add(unlocker); + + return unlockers; + } + + // Define object serialization, that should serialize both parent class entries and CarBuyOrderInfo as well + @Override + public byte[] bytes() { + ByteArrayOutputStream inputsIdsStream = new ByteArrayOutputStream(); + for(byte[] id: inputRegularBoxIds) + inputsIdsStream.write(id, 0, id.length); + + byte[] inputRegularBoxIdsBytes = inputsIdsStream.toByteArray(); + + byte[] inputRegularBoxProofsBytes = regularBoxProofsSerializer.toBytes(inputRegularBoxProofs); + + byte[] outputRegularBoxesDataBytes = regularBoxDataListSerializer.toBytes(outputRegularBoxesData); + + byte[] carBuyOrderInfoBytes = carBuyOrderInfo.bytes(); + + return Bytes.concat( + Longs.toByteArray(fee()), // 8 bytes + Longs.toByteArray(timestamp()), // 8 bytes + Ints.toByteArray(inputRegularBoxIdsBytes.length), // 4 bytes + inputRegularBoxIdsBytes, // depends on previous value (>=4 bytes) + Ints.toByteArray(inputRegularBoxProofsBytes.length), // 4 bytes + inputRegularBoxProofsBytes, // depends on previous value (>=4 bytes) + Ints.toByteArray(outputRegularBoxesDataBytes.length), // 4 bytes + outputRegularBoxesDataBytes, // depends on previous value (>=4 bytes) + Ints.toByteArray(carBuyOrderInfoBytes.length), // 4 bytes + carBuyOrderInfoBytes // depends on previous value (>=4 bytes) + ); + } + + // Define object deserialization similar to 'toBytes()' representation. + public static BuyCarTransaction parseBytes(byte[] bytes) { + int offset = 0; + + long fee = BytesUtils.getLong(bytes, offset); + offset += 8; + + long timestamp = BytesUtils.getLong(bytes, offset); + offset += 8; + + int batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + ArrayList inputRegularBoxIds = new ArrayList<>(); + int idLength = NodeViewModifier$.MODULE$.ModifierIdSize(); + while(batchSize > 0) { + inputRegularBoxIds.add(Arrays.copyOfRange(bytes, offset, offset + idLength)); + offset += idLength; + batchSize -= idLength; + } + + batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + List inputRegularBoxProofs = regularBoxProofsSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); + offset += batchSize; + + batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + List outputRegularBoxesData = regularBoxDataListSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); + offset += batchSize; + + batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + CarBuyOrderInfo carBuyOrderInfo = CarBuyOrderInfo.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); + + return new BuyCarTransaction(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, carBuyOrderInfo, fee, timestamp); + } + + // Set specific Serializer for BuyCarTransaction class. + @Override + public TransactionSerializer serializer() { + return BuyCarTransactionSerializer.getSerializer(); + } +} diff --git a/src/main/java/io/horizen/lambo/car/transaction/CarDeclarationTransaction.java b/src/main/java/io/horizen/lambo/car/transaction/CarDeclarationTransaction.java index c55dfda..2d74db7 100644 --- a/src/main/java/io/horizen/lambo/car/transaction/CarDeclarationTransaction.java +++ b/src/main/java/io/horizen/lambo/car/transaction/CarDeclarationTransaction.java @@ -1,139 +1,135 @@ -package io.horizen.lambo.car.transaction; - -import com.google.common.primitives.Bytes; -import com.google.common.primitives.Ints; -import com.google.common.primitives.Longs; -import com.horizen.box.NoncedBox; -import com.horizen.box.data.RegularBoxData; -import io.horizen.lambo.car.box.CarBox; -import io.horizen.lambo.car.box.data.CarBoxData; -import io.horizen.lambo.car.box.data.CarBoxDataSerializer; -import com.horizen.proof.Signature25519; -import com.horizen.proposition.Proposition; -import com.horizen.transaction.TransactionSerializer; -import com.horizen.utils.BytesUtils; -import scorex.core.NodeViewModifier$; - -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static io.horizen.lambo.car.transaction.CarRegistryTransactionsIdsEnum.CarDeclarationTransactionId; - -// CarDeclarationTransaction is nested from AbstractRegularTransaction so support regular coins transmission as well. -// Moreover it was designed to declare new Cars in the sidechain network. -// As outputs it contains possible RegularBoxes(to pay fee and change) and new CarBox entry. -// No specific unlockers to parent class logic, but has specific new box. -// TODO: add specific mempool incompatibility checker to deprecate keeping in the Mempool txs that declare the same Car. -public final class CarDeclarationTransaction extends AbstractRegularTransaction { - - private final CarBoxData outputCarBoxData; - - private List> newBoxes; - - public CarDeclarationTransaction(List inputRegularBoxIds, - List inputRegularBoxProofs, - List outputRegularBoxesData, - CarBoxData outputCarBoxData, - long fee, - long timestamp) { - super(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, fee, timestamp); - this.outputCarBoxData = outputCarBoxData; - } - - // Specify the unique custom transaction id. - @Override - public byte transactionTypeId() { - return CarDeclarationTransactionId.id(); - } - - // Override newBoxes to contains regularBoxes from the parent class appended with CarBox entry. - // The nonce calculation algorithm for CarBox is the same as in parent class. - @Override - public synchronized List> newBoxes() { - if(newBoxes == null) { - newBoxes = new ArrayList<>(super.newBoxes()); - long nonce = getNewBoxNonce(outputCarBoxData.proposition(), newBoxes.size()); - newBoxes.add((NoncedBox) new CarBox(outputCarBoxData, nonce)); - } - return Collections.unmodifiableList(newBoxes); - } - - // Define object serialization, that should serialize both parent class entries and CarBoxData as well - @Override - public byte[] bytes() { - ByteArrayOutputStream inputsIdsStream = new ByteArrayOutputStream(); - for(byte[] id: inputRegularBoxIds) - inputsIdsStream.write(id, 0, id.length); - - byte[] inputRegularBoxIdsBytes = inputsIdsStream.toByteArray(); - - byte[] inputRegularBoxProofsBytes = regularBoxProofsSerializer.toBytes(inputRegularBoxProofs); - - byte[] outputRegularBoxesDataBytes = regularBoxDataListSerializer.toBytes(outputRegularBoxesData); - - byte[] outputCarBoxDataBytes = outputCarBoxData.bytes(); - - return Bytes.concat( - Longs.toByteArray(fee()), // 8 bytes - Longs.toByteArray(timestamp()), // 8 bytes - Ints.toByteArray(inputRegularBoxIdsBytes.length), // 4 bytes - inputRegularBoxIdsBytes, // depends on previous value (>=4 bytes) - Ints.toByteArray(inputRegularBoxProofsBytes.length), // 4 bytes - inputRegularBoxProofsBytes, // depends on previous value (>=4 bytes) - Ints.toByteArray(outputRegularBoxesDataBytes.length), // 4 bytes - outputRegularBoxesDataBytes, // depends on previous value (>=4 bytes) - Ints.toByteArray(outputCarBoxDataBytes.length), // 4 bytes - outputCarBoxDataBytes // depends on previous value (>=4 bytes) - ); - } - - // Define object deserialization similar to 'toBytes()' representation. - public static CarDeclarationTransaction parseBytes(byte[] bytes) { - int offset = 0; - - long fee = BytesUtils.getLong(bytes, offset); - offset += 8; - - long timestamp = BytesUtils.getLong(bytes, offset); - offset += 8; - - int batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - ArrayList inputRegularBoxIds = new ArrayList<>(); - int idLength = NodeViewModifier$.MODULE$.ModifierIdSize(); - while(batchSize > 0) { - inputRegularBoxIds.add(Arrays.copyOfRange(bytes, offset, offset + idLength)); - offset += idLength; - batchSize -= idLength; - } - - batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - List inputRegularBoxProofs = regularBoxProofsSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); - offset += batchSize; - - batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - List outputRegularBoxesData = regularBoxDataListSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); - offset += batchSize; - - batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - CarBoxData outputCarBoxData = CarBoxDataSerializer.getSerializer().parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); - - return new CarDeclarationTransaction(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, outputCarBoxData, fee, timestamp); - } - - // Set specific Serializer for CarDeclarationTransaction class. - @Override - public TransactionSerializer serializer() { - return CarDeclarationTransactionSerializer.getSerializer(); - } -} +package io.horizen.lambo.car.transaction; + +import com.google.common.primitives.Bytes; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.horizen.box.NoncedBox; +import com.horizen.box.data.NoncedBoxData; +import com.horizen.box.data.RegularBoxData; +import io.horizen.lambo.car.box.data.CarBoxData; +import io.horizen.lambo.car.box.data.CarBoxDataSerializer; +import com.horizen.proof.Signature25519; +import com.horizen.proposition.Proposition; +import com.horizen.transaction.TransactionSerializer; +import com.horizen.transaction.AbstractRegularTransaction; +import com.horizen.utils.BytesUtils; +import scorex.core.NodeViewModifier$; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static io.horizen.lambo.car.transaction.CarRegistryTransactionsIdsEnum.CarDeclarationTransactionId; + +// CarDeclarationTransaction is nested from AbstractRegularTransaction so support regular coins transmission as well. +// Moreover it was designed to declare new Cars in the sidechain network. +// As outputs it contains possible RegularBoxes(to pay fee and change) and new CarBox entry. +// No specific unlockers to parent class logic, but has specific new box. +// TODO: add specific mempool incompatibility checker to deprecate keeping in the Mempool txs that declare the same Car. +public final class CarDeclarationTransaction extends AbstractRegularTransaction { + + private final CarBoxData outputCarBoxData; + + public CarDeclarationTransaction(List inputRegularBoxIds, + List inputRegularBoxProofs, + List outputRegularBoxesData, + CarBoxData outputCarBoxData, + long fee, + long timestamp) { + super(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, fee, timestamp); + + // Parameters sanity check + if(outputCarBoxData == null){ + throw new IllegalArgumentException("Unacceptable value of outputCarBoxData!"); + } + this.outputCarBoxData = outputCarBoxData; + } + + // Specify the unique custom transaction id. + @Override + public byte transactionTypeId() { + return CarDeclarationTransactionId.id(); + } + + @Override + protected List>> getCustomOutputData(){ + return Arrays.asList((NoncedBoxData) outputCarBoxData); + } + + // Define object serialization, that should serialize both parent class entries and CarBoxData as well + @Override + public byte[] bytes() { + ByteArrayOutputStream inputsIdsStream = new ByteArrayOutputStream(); + for(byte[] id: inputRegularBoxIds) + inputsIdsStream.write(id, 0, id.length); + + byte[] inputRegularBoxIdsBytes = inputsIdsStream.toByteArray(); + + byte[] inputRegularBoxProofsBytes = regularBoxProofsSerializer.toBytes(inputRegularBoxProofs); + + byte[] outputRegularBoxesDataBytes = regularBoxDataListSerializer.toBytes(outputRegularBoxesData); + + byte[] outputCarBoxDataBytes = outputCarBoxData.bytes(); + + return Bytes.concat( + Longs.toByteArray(fee()), // 8 bytes + Longs.toByteArray(timestamp()), // 8 bytes + Ints.toByteArray(inputRegularBoxIdsBytes.length), // 4 bytes + inputRegularBoxIdsBytes, // depends on previous value (>=4 bytes) + Ints.toByteArray(inputRegularBoxProofsBytes.length), // 4 bytes + inputRegularBoxProofsBytes, // depends on previous value (>=4 bytes) + Ints.toByteArray(outputRegularBoxesDataBytes.length), // 4 bytes + outputRegularBoxesDataBytes, // depends on previous value (>=4 bytes) + Ints.toByteArray(outputCarBoxDataBytes.length), // 4 bytes + outputCarBoxDataBytes // depends on previous value (>=4 bytes) + ); + } + + // Define object deserialization similar to 'toBytes()' representation. + public static CarDeclarationTransaction parseBytes(byte[] bytes) { + int offset = 0; + + long fee = BytesUtils.getLong(bytes, offset); + offset += 8; + + long timestamp = BytesUtils.getLong(bytes, offset); + offset += 8; + + int batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + ArrayList inputRegularBoxIds = new ArrayList<>(); + int idLength = NodeViewModifier$.MODULE$.ModifierIdSize(); + while(batchSize > 0) { + inputRegularBoxIds.add(Arrays.copyOfRange(bytes, offset, offset + idLength)); + offset += idLength; + batchSize -= idLength; + } + + batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + List inputRegularBoxProofs = regularBoxProofsSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); + offset += batchSize; + + batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + List outputRegularBoxesData = regularBoxDataListSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); + offset += batchSize; + + batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + CarBoxData outputCarBoxData = CarBoxDataSerializer.getSerializer().parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); + + return new CarDeclarationTransaction(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, outputCarBoxData, fee, timestamp); + } + + // Set specific Serializer for CarDeclarationTransaction class. + @Override + public TransactionSerializer serializer() { + return CarDeclarationTransactionSerializer.getSerializer(); + } +} diff --git a/src/main/java/io/horizen/lambo/car/transaction/SellCarTransaction.java b/src/main/java/io/horizen/lambo/car/transaction/SellCarTransaction.java index ff20cc6..fbea4ae 100644 --- a/src/main/java/io/horizen/lambo/car/transaction/SellCarTransaction.java +++ b/src/main/java/io/horizen/lambo/car/transaction/SellCarTransaction.java @@ -1,170 +1,164 @@ -package io.horizen.lambo.car.transaction; - -import com.google.common.primitives.Bytes; -import com.google.common.primitives.Ints; -import com.google.common.primitives.Longs; -import com.horizen.box.BoxUnlocker; -import com.horizen.box.NoncedBox; -import com.horizen.box.data.RegularBoxData; -import io.horizen.lambo.car.box.CarSellOrderBox; -import io.horizen.lambo.car.info.CarSellOrderInfo; -import com.horizen.proof.Proof; -import com.horizen.proof.Signature25519; -import com.horizen.proposition.Proposition; -import com.horizen.transaction.TransactionSerializer; -import com.horizen.utils.BytesUtils; -import scorex.core.NodeViewModifier$; - -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static io.horizen.lambo.car.transaction.CarRegistryTransactionsIdsEnum.SellCarTransactionId; - -// SellCarTransaction is nested from AbstractRegularTransaction so support regular coins transmission as well. -// SellCarTransaction was designed to create a SellOrder for a specific buyer for given CarBox owned by the user. -// As outputs it contains possible RegularBoxes(to pay fee and make change) and new CarSellOrder entry. -// As unlockers it contains RegularBoxes and CarBox to open. -public final class SellCarTransaction extends AbstractRegularTransaction { - - // CarSellOrderInfo is a view that describes what car box to open and what is the sell order(car attributes, price and buyer info). - // But inside it contains just a minimum set of info (like CarBox itself and price) that is the unique source of data. - // So, no one outside controls what will be the specific outputs of this transaction. - // Any malicious actions will lead to transaction invalidation. - // For example, if CarBox was opened, the CarSellOrder obliged to contains the same car attributes and owner info. - private final CarSellOrderInfo carSellOrderInfo; - - private List> newBoxes; - - public SellCarTransaction(List inputRegularBoxIds, - List inputRegularBoxProofs, - List outputRegularBoxesData, - CarSellOrderInfo carSellOrderInfo, - long fee, - long timestamp) { - super(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, fee, timestamp); - this.carSellOrderInfo = carSellOrderInfo; - } - - // Specify the unique custom transaction id. - @Override - public byte transactionTypeId() { - return SellCarTransactionId.id(); - } - - // Override unlockers to contains regularBoxes from the parent class appended with CarBox entry to be opened. - @Override - public List> unlockers() { - // Get Regular unlockers from base class. - List> unlockers = super.unlockers(); - - BoxUnlocker unlocker = new BoxUnlocker() { - @Override - public byte[] closedBoxId() { - return carSellOrderInfo.getCarBoxToOpen().id(); - } - - @Override - public Proof boxKey() { - return carSellOrderInfo.getCarBoxSpendingProof(); - } - }; - // Append with the CarBox unlocker entry. - unlockers.add(unlocker); - - return unlockers; - } - - // Override newBoxes to contains regularBoxes from the parent class appended with CarSellOrderBox and payment entries. - // The nonce calculation algorithm for CarSellOrderBox is the same as in parent class. - @Override - public List> newBoxes() { - if(newBoxes == null) { - newBoxes = new ArrayList<>(super.newBoxes()); - long nonce = getNewBoxNonce(carSellOrderInfo.getSellOrderBoxData().proposition(), newBoxes.size()); - // Here we enforce output CarSellOrder data calculation. - // Any malicious action will lead to different inconsistent data to the honest nodes State. - newBoxes.add((NoncedBox) new CarSellOrderBox(carSellOrderInfo.getSellOrderBoxData(), nonce)); - - } - return Collections.unmodifiableList(newBoxes); - } - - // Define object serialization, that should serialize both parent class entries and CarSellOrderInfo as well - @Override - public byte[] bytes() { - ByteArrayOutputStream inputsIdsStream = new ByteArrayOutputStream(); - for(byte[] id: inputRegularBoxIds) - inputsIdsStream.write(id, 0, id.length); - - byte[] inputRegularBoxIdsBytes = inputsIdsStream.toByteArray(); - - byte[] inputRegularBoxProofsBytes = regularBoxProofsSerializer.toBytes(inputRegularBoxProofs); - - byte[] outputRegularBoxesDataBytes = regularBoxDataListSerializer.toBytes(outputRegularBoxesData); - - byte[] carSellOrderInfoBytes = carSellOrderInfo.bytes(); - - return Bytes.concat( - Longs.toByteArray(fee()), // 8 bytes - Longs.toByteArray(timestamp()), // 8 bytes - Ints.toByteArray(inputRegularBoxIdsBytes.length), // 4 bytes - inputRegularBoxIdsBytes, // depends on previous value (>=4 bytes) - Ints.toByteArray(inputRegularBoxProofsBytes.length), // 4 bytes - inputRegularBoxProofsBytes, // depends on previous value (>=4 bytes) - Ints.toByteArray(outputRegularBoxesDataBytes.length), // 4 bytes - outputRegularBoxesDataBytes, // depends on previous value (>=4 bytes) - Ints.toByteArray(carSellOrderInfoBytes.length), // 4 bytes - carSellOrderInfoBytes // depends on previous value (>=4 bytes) - ); - } - - // Define object deserialization similar to 'toBytes()' representation. - public static SellCarTransaction parseBytes(byte[] bytes) { - int offset = 0; - - long fee = BytesUtils.getLong(bytes, offset); - offset += 8; - - long timestamp = BytesUtils.getLong(bytes, offset); - offset += 8; - - int batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - ArrayList inputRegularBoxIds = new ArrayList<>(); - int idLength = NodeViewModifier$.MODULE$.ModifierIdSize(); - while(batchSize > 0) { - inputRegularBoxIds.add(Arrays.copyOfRange(bytes, offset, offset + idLength)); - offset += idLength; - batchSize -= idLength; - } - - batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - List inputRegularBoxProofs = regularBoxProofsSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); - offset += batchSize; - - batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - List outputRegularBoxesData = regularBoxDataListSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); - offset += batchSize; - - batchSize = BytesUtils.getInt(bytes, offset); - offset += 4; - - CarSellOrderInfo carSellOrderInfo = CarSellOrderInfo.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); - - return new SellCarTransaction(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, carSellOrderInfo, fee, timestamp); - } - - // Set specific Serializer for SellCarTransaction class. - @Override - public TransactionSerializer serializer() { - return SellCarTransactionSerializer.getSerializer(); - } -} \ No newline at end of file +package io.horizen.lambo.car.transaction; + +import com.google.common.primitives.Bytes; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.horizen.box.BoxUnlocker; +import com.horizen.box.NoncedBox; +import com.horizen.box.data.NoncedBoxData; +import com.horizen.box.data.RegularBoxData; +import io.horizen.lambo.car.info.CarSellOrderInfo; +import com.horizen.proof.Proof; +import com.horizen.proof.Signature25519; +import com.horizen.proposition.Proposition; +import com.horizen.transaction.TransactionSerializer; +import com.horizen.transaction.AbstractRegularTransaction; +import com.horizen.utils.BytesUtils; +import scorex.core.NodeViewModifier$; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static io.horizen.lambo.car.transaction.CarRegistryTransactionsIdsEnum.SellCarTransactionId; + +// SellCarTransaction is nested from AbstractRegularTransaction so support regular coins transmission as well. +// SellCarTransaction was designed to create a SellOrder for a specific buyer for given CarBox owned by the user. +// As outputs it contains possible RegularBoxes(to pay fee and make change) and new CarSellOrder entry. +// As unlockers it contains RegularBoxes and CarBox to open. +public final class SellCarTransaction extends AbstractRegularTransaction { + + // CarSellOrderInfo is a view that describes what car box to open and what is the sell order(car attributes, price and buyer info). + // But inside it contains just a minimum set of info (like CarBox itself and price) that is the unique source of data. + // So, no one outside controls what will be the specific outputs of this transaction. + // Any malicious actions will lead to transaction invalidation. + // For example, if CarBox was opened, the CarSellOrder obliged to contains the same car attributes and owner info. + private final CarSellOrderInfo carSellOrderInfo; + + public SellCarTransaction(List inputRegularBoxIds, + List inputRegularBoxProofs, + List outputRegularBoxesData, + CarSellOrderInfo carSellOrderInfo, + long fee, + long timestamp) { + super(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, fee, timestamp); + + // Parameters sanity check + if(carSellOrderInfo == null){ + throw new IllegalArgumentException("Unacceptable value of carSellOrderInfo!"); + } + this.carSellOrderInfo = carSellOrderInfo; + } + + // Specify the unique custom transaction id. + @Override + public byte transactionTypeId() { + return SellCarTransactionId.id(); + } + + // Gets CarSellOrderInfo-related boxes data + @Override + protected List>> getCustomOutputData(){ + return Arrays.asList((NoncedBoxData) carSellOrderInfo.getSellOrderBoxData()); + } + + // Override unlockers to contains regularBoxes from the parent class appended with CarBox entry to be opened. + @Override + public List> unlockers() { + // Get Regular unlockers from base class. + List> unlockers = super.unlockers(); + + BoxUnlocker unlocker = new BoxUnlocker() { + @Override + public byte[] closedBoxId() { + return carSellOrderInfo.getCarBoxToOpen().id(); + } + + @Override + public Proof boxKey() { + return carSellOrderInfo.getCarBoxSpendingProof(); + } + }; + // Append with the CarBox unlocker entry. + unlockers.add(unlocker); + + return unlockers; + } + + // Define object serialization, that should serialize both parent class entries and CarSellOrderInfo as well + @Override + public byte[] bytes() { + ByteArrayOutputStream inputsIdsStream = new ByteArrayOutputStream(); + for(byte[] id: inputRegularBoxIds) + inputsIdsStream.write(id, 0, id.length); + + byte[] inputRegularBoxIdsBytes = inputsIdsStream.toByteArray(); + + byte[] inputRegularBoxProofsBytes = regularBoxProofsSerializer.toBytes(inputRegularBoxProofs); + + byte[] outputRegularBoxesDataBytes = regularBoxDataListSerializer.toBytes(outputRegularBoxesData); + + byte[] carSellOrderInfoBytes = carSellOrderInfo.bytes(); + + return Bytes.concat( + Longs.toByteArray(fee()), // 8 bytes + Longs.toByteArray(timestamp()), // 8 bytes + Ints.toByteArray(inputRegularBoxIdsBytes.length), // 4 bytes + inputRegularBoxIdsBytes, // depends on previous value (>=4 bytes) + Ints.toByteArray(inputRegularBoxProofsBytes.length), // 4 bytes + inputRegularBoxProofsBytes, // depends on previous value (>=4 bytes) + Ints.toByteArray(outputRegularBoxesDataBytes.length), // 4 bytes + outputRegularBoxesDataBytes, // depends on previous value (>=4 bytes) + Ints.toByteArray(carSellOrderInfoBytes.length), // 4 bytes + carSellOrderInfoBytes // depends on previous value (>=4 bytes) + ); + } + + // Define object deserialization similar to 'toBytes()' representation. + public static SellCarTransaction parseBytes(byte[] bytes) { + int offset = 0; + + long fee = BytesUtils.getLong(bytes, offset); + offset += 8; + + long timestamp = BytesUtils.getLong(bytes, offset); + offset += 8; + + int batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + ArrayList inputRegularBoxIds = new ArrayList<>(); + int idLength = NodeViewModifier$.MODULE$.ModifierIdSize(); + while(batchSize > 0) { + inputRegularBoxIds.add(Arrays.copyOfRange(bytes, offset, offset + idLength)); + offset += idLength; + batchSize -= idLength; + } + + batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + List inputRegularBoxProofs = regularBoxProofsSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); + offset += batchSize; + + batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + List outputRegularBoxesData = regularBoxDataListSerializer.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); + offset += batchSize; + + batchSize = BytesUtils.getInt(bytes, offset); + offset += 4; + + CarSellOrderInfo carSellOrderInfo = CarSellOrderInfo.parseBytes(Arrays.copyOfRange(bytes, offset, offset + batchSize)); + + return new SellCarTransaction(inputRegularBoxIds, inputRegularBoxProofs, outputRegularBoxesData, carSellOrderInfo, fee, timestamp); + } + + // Set specific Serializer for SellCarTransaction class. + @Override + public TransactionSerializer serializer() { + return SellCarTransactionSerializer.getSerializer(); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/fixtures/BoxFixtures.java b/src/test/java/io/horizen/lambo/transactions/fixtures/BoxFixtures.java new file mode 100644 index 0000000..27889cf --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/fixtures/BoxFixtures.java @@ -0,0 +1,40 @@ +package io.horizen.lambo.transactions.fixtures; + +import com.horizen.box.data.RegularBoxData; +import com.horizen.proof.Signature25519; +import com.horizen.proposition.PublicKey25519Proposition; +import com.horizen.secret.PrivateKey25519; +import com.horizen.secret.PrivateKey25519Creator; +import scorex.core.NodeViewModifier$; + +import java.util.Random; + +// Methods for generating random cryptographic objects for tests +public class BoxFixtures { + + public static byte[] getRandomBoxId() { + byte[] id = new byte[NodeViewModifier$.MODULE$.ModifierIdSize()]; + new Random().nextBytes(id); + return id; + } + + public static PrivateKey25519 getPrivateKey25519() { + byte[] seed = new byte[32]; + new Random().nextBytes(seed); + return PrivateKey25519Creator.getInstance().generateSecret(seed); + } + + public static Signature25519 getRandomSignature25519(){ + PrivateKey25519 pk = getPrivateKey25519(); + byte[] message = "12345".getBytes(); + return pk.sign(message); + } + + public static PublicKey25519Proposition getPublicKey25519Proposition(){ + return getPrivateKey25519().publicImage(); + } + + public static RegularBoxData getRegularBoxData() { + return new RegularBoxData(getPublicKey25519Proposition(), new Random().nextInt(100)); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/AbstractParameters.java b/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/AbstractParameters.java new file mode 100644 index 0000000..c8c579b --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/AbstractParameters.java @@ -0,0 +1,111 @@ +package io.horizen.lambo.transactions.fixtures.parameters; + +import com.horizen.box.data.RegularBoxData; +import com.horizen.proof.Signature25519; +import com.horizen.transaction.AbstractRegularTransaction; +import io.horizen.lambo.car.transaction.BuyCarTransaction; +import io.horizen.lambo.car.transaction.CarDeclarationTransaction; +import io.horizen.lambo.car.transaction.SellCarTransaction; + +import java.util.*; + +import static io.horizen.lambo.transactions.fixtures.BoxFixtures.*; + +// This abstract class defines parameters of the AbstractRegularTransaction +abstract public class AbstractParameters { + + // Parameters for the AbstractRegularTransaction which is a base for the Lambo-transactions + public final List inputsIds; + public final List proofs; + public final List outputsData; + public final long fee; + public final long timestamp; + + // Initialization of the AbstractRegularTransaction parameters is random by default + AbstractParameters(){ + // List of 2 random Ids + inputsIds = Arrays.asList(getRandomBoxId(), getRandomBoxId()); + + // List of 2 random proofs + proofs = new ArrayList<>(); + proofs.add(getRandomSignature25519()); + proofs.add(getRandomSignature25519()); + + // List of one output box + outputsData = new ArrayList<>(); + outputsData.add(getRegularBoxData()); + + // Random non-negative fee and timestamp + fee = new Random().nextInt(10000); + timestamp = new Random().nextInt(10000); + } + + AbstractParameters( + List inputsIds, + List proofs, + List outputsData, + long fee, + long timestamp){ + this.inputsIds = inputsIds; + this.proofs = proofs; + this.outputsData = outputsData; + this.fee = fee; + this.timestamp = timestamp; + } + + // Creates a new instance of an AbstractParameters inheritor + abstract AbstractParameters newParams( + List inputsIds, + List proofs, + List outputsData, + long fee, + long timestamp); + + // Methods for creating a new instance of the current parameters with re-initialization of one of them with a specified value + public AbstractParameters inputsIds(List _inputsIds){ + return newParams(_inputsIds, proofs, outputsData, fee, timestamp); + } + public AbstractParameters proofs(List _proofs){ + return newParams(inputsIds, _proofs, outputsData, fee, timestamp); + } + public AbstractParameters outputsData(List _outputsData){ + return newParams(inputsIds, proofs, _outputsData, fee, timestamp); + } + public AbstractParameters fee(long _fee) { + return newParams(inputsIds, proofs, outputsData, _fee, timestamp); + } + public AbstractParameters timestamp(long _timestamp) { + return newParams(inputsIds, proofs, outputsData, fee, _timestamp); + } + + // Static method for creating a transaction which corresponds to a current parameters set + // The Lambo-transactions constructors can throw an exception so an exception handler is used + private static Optional tryToCreate(AbstractParameters p){ + Optional transactionOpt = Optional.empty(); + try { + if(p instanceof CarDeclarationParameters){ + transactionOpt = Optional.of(new CarDeclarationTransaction(p.inputsIds, p.proofs, p.outputsData, ((CarDeclarationParameters)p).carBoxData, p.fee, p.timestamp)); + } + else if(p instanceof SellCarParameters){ + transactionOpt = Optional.of(new SellCarTransaction(p.inputsIds, p.proofs, p.outputsData, ((SellCarParameters)p).carSellOrderInfo, p.fee, p.timestamp)); + } + else if(p instanceof BuyCarParameters){ + transactionOpt = Optional.of(new BuyCarTransaction(p.inputsIds, p.proofs, p.outputsData, ((BuyCarParameters)p).carBuyOrderInfo, p.fee, p.timestamp)); + } + } + catch (Exception e) { + return Optional.empty(); + } + return transactionOpt; + } + + // Checks if transaction can be created from the specified parameters + public static boolean canCreate(AbstractParameters p){ + return tryToCreate(p).isPresent(); + } + + // Creates transaction assuming that specified parameters are correct + public static AbstractRegularTransaction create(AbstractParameters p){ + return tryToCreate(p).get(); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/BuyCarParameters.java b/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/BuyCarParameters.java new file mode 100644 index 0000000..88eaff6 --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/BuyCarParameters.java @@ -0,0 +1,53 @@ +package io.horizen.lambo.transactions.fixtures.parameters; + +import com.horizen.box.data.RegularBoxData; +import com.horizen.proof.Signature25519; +import io.horizen.lambo.car.box.CarSellOrderBox; +import io.horizen.lambo.car.box.data.CarSellOrderBoxData; +import io.horizen.lambo.car.info.CarBuyOrderInfo; +import io.horizen.lambo.car.proof.SellOrderSpendingProof; +import io.horizen.lambo.car.proposition.SellOrderProposition; + +import java.util.List; +import java.util.Random; + +import static io.horizen.lambo.transactions.fixtures.BoxFixtures.getPublicKey25519Proposition; +import static io.horizen.lambo.transactions.fixtures.BoxFixtures.getRandomSignature25519; + +public class BuyCarParameters extends AbstractParameters { + public final CarBuyOrderInfo carBuyOrderInfo; + + public BuyCarParameters(){ + SellOrderProposition carSellOrderProposition = new SellOrderProposition(getPublicKey25519Proposition().bytes(), getPublicKey25519Proposition().bytes()); + CarSellOrderBox carSellOrderBoxBox = new CarSellOrderBox(new CarSellOrderBoxData(carSellOrderProposition, 100, "vin", 1000, "model", "color"), new Random().nextInt()); + SellOrderSpendingProof proof = new SellOrderSpendingProof(getRandomSignature25519().bytes(), true); + + carBuyOrderInfo = new CarBuyOrderInfo(carSellOrderBoxBox, proof); + } + + public BuyCarParameters( + List inputsIds, + List proofs, + List outputsData, + CarBuyOrderInfo carBuyOrderInfo, + long fee, + long timestamp){ + super(inputsIds, proofs, outputsData, fee, timestamp); + this.carBuyOrderInfo = carBuyOrderInfo; + } + + // Creates a new instance of the current parameters with re-initialization of carBuyOrderInfo + public BuyCarParameters buyOrderInfo(CarBuyOrderInfo _buyOrderInfo) { + return new BuyCarParameters(inputsIds, proofs, outputsData, _buyOrderInfo, fee, timestamp); + } + + @Override + BuyCarParameters newParams( + List inputsIds, + List proofs, + List outputsData, + long fee, + long timestamp){ + return new BuyCarParameters(inputsIds, proofs, outputsData, carBuyOrderInfo, fee, timestamp); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/CarDeclarationParameters.java b/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/CarDeclarationParameters.java new file mode 100644 index 0000000..3d57501 --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/CarDeclarationParameters.java @@ -0,0 +1,43 @@ +package io.horizen.lambo.transactions.fixtures.parameters; + +import com.horizen.box.data.RegularBoxData; +import com.horizen.proof.Signature25519; +import io.horizen.lambo.car.box.data.CarBoxData; + +import java.util.List; + +import static io.horizen.lambo.transactions.fixtures.BoxFixtures.getPublicKey25519Proposition; + +public class CarDeclarationParameters extends AbstractParameters { + public final CarBoxData carBoxData; + + public CarDeclarationParameters(){ + carBoxData = new CarBoxData(getPublicKey25519Proposition(), "vin", 1000, "model", "color"); + } + + public CarDeclarationParameters( + List inputsIds, + List proofs, + List outputsData, + CarBoxData carBoxData, + long fee, + long timestamp){ + super(inputsIds, proofs, outputsData, fee, timestamp); + this.carBoxData = carBoxData; + } + + // Creates a new instance of the current parameters with re-initialization of carBoxData + public CarDeclarationParameters boxData(CarBoxData _boxData) { + return new CarDeclarationParameters(inputsIds, proofs, outputsData, _boxData, fee, timestamp); + } + + @Override + CarDeclarationParameters newParams( + List inputsIds, + List proofs, + List outputsData, + long fee, + long timestamp){ + return new CarDeclarationParameters(inputsIds, proofs, outputsData, carBoxData, fee, timestamp); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/SellCarParameters.java b/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/SellCarParameters.java new file mode 100644 index 0000000..f4ad04a --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/fixtures/parameters/SellCarParameters.java @@ -0,0 +1,48 @@ +package io.horizen.lambo.transactions.fixtures.parameters; + +import com.horizen.box.data.RegularBoxData; +import com.horizen.proof.Signature25519; +import io.horizen.lambo.car.box.CarBox; +import io.horizen.lambo.car.box.data.CarBoxData; +import io.horizen.lambo.car.info.CarSellOrderInfo; + +import java.util.List; +import java.util.Random; + +import static io.horizen.lambo.transactions.fixtures.BoxFixtures.getPublicKey25519Proposition; +import static io.horizen.lambo.transactions.fixtures.BoxFixtures.getRandomSignature25519; + +public class SellCarParameters extends AbstractParameters { + public final CarSellOrderInfo carSellOrderInfo; + + public SellCarParameters(){ + CarBox carBox = new CarBox(new CarBoxData(getPublicKey25519Proposition(), "vin", 1000, "model", "color"), new Random().nextInt()); + carSellOrderInfo = new CarSellOrderInfo(carBox, getRandomSignature25519(), 100, getPublicKey25519Proposition()); + } + + public SellCarParameters( + List inputsIds, + List proofs, + List outputsData, + CarSellOrderInfo carSellOrderInfo, + long fee, + long timestamp){ + super(inputsIds, proofs, outputsData, fee, timestamp); + this.carSellOrderInfo = carSellOrderInfo; + } + + // Creates a new instance of the current parameters with re-initialization of carSellOrderInfo + public SellCarParameters sellOrderInfo(CarSellOrderInfo _sellOrderInfo) { + return new SellCarParameters(inputsIds, proofs, outputsData, _sellOrderInfo, fee, timestamp); + } + + @Override + SellCarParameters newParams( + List inputsIds, + List proofs, + List outputsData, + long fee, + long timestamp){ + return new SellCarParameters(inputsIds, proofs, outputsData, carSellOrderInfo, fee, timestamp); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/functionality/AbstractRegularTransactionTest.java b/src/test/java/io/horizen/lambo/transactions/functionality/AbstractRegularTransactionTest.java new file mode 100644 index 0000000..44908bf --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/functionality/AbstractRegularTransactionTest.java @@ -0,0 +1,48 @@ +package io.horizen.lambo.transactions.functionality; + +import io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters; + +import java.util.Collections; + +import static io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters.canCreate; +import static io.horizen.lambo.transactions.fixtures.BoxFixtures.getRandomBoxId; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +// Set of tests for the AbstractRegularTransaction parameters +// This type of transaction can't be instantiated individually, so this set of tests should be applied to any of AbstractRegularTransaction inheritors +public class AbstractRegularTransactionTest { + + public static void creation(AbstractParameters params){ + + // Test 1: everything is correct + assertTrue("Test1: Successful transaction creation expected", canCreate(params)); + + // Test 2: inputs ids is null + assertFalse("Test2: Exception expected", canCreate(params.inputsIds(null))); + + // Test 3: proofs is null + assertFalse("Test3: Exception expected", canCreate(params.proofs(null))); + + // Test 4: outputs data is null + assertFalse("Test4: Exception expected", canCreate(params.outputsData(null))); + + // Test 5: inputs ids list is empty + assertFalse("Test5: Exception expected", canCreate(params.inputsIds(Collections.emptyList()))); + + // Test 6: proofs list is empty + assertFalse("Test6: Exception expected", canCreate(params.proofs(Collections.emptyList()))); + + // Test 7: outputs data list is empty + assertFalse("Test7: Exception expected", canCreate(params.outputsData(Collections.emptyList()))); + + // Test 9: fee is negative + assertFalse("Test8: Exception expected", canCreate(params.fee(-2))); + + // Test 9: timestamp is negative + assertFalse("Test9: Exception expected", canCreate(params.timestamp(-2))); + + // Test 10: number of inputs (1) is different to number of proofs (2) + assertFalse("Test10: Exception expected", canCreate(params.inputsIds(Collections.singletonList(getRandomBoxId())))); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/functionality/BuyCarTransactionTest.java b/src/test/java/io/horizen/lambo/transactions/functionality/BuyCarTransactionTest.java new file mode 100644 index 0000000..0f646c7 --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/functionality/BuyCarTransactionTest.java @@ -0,0 +1,42 @@ +package io.horizen.lambo.transactions.functionality; + +import io.horizen.lambo.transactions.fixtures.parameters.BuyCarParameters; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +import static io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters.canCreate; +import static io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters.create; +import static io.horizen.lambo.transactions.fixtures.BoxFixtures.*; +import static org.junit.Assert.*; + + +public class BuyCarTransactionTest { + + BuyCarParameters validParams; + + @Before + public void initialiazeParams(){ validParams = new BuyCarParameters(); } + + @Test + public void creation(){ + // Tests for the common functionality + AbstractRegularTransactionTest.creation(validParams); + + // SellCar specific test: buyOrderInfo is null + assertFalse("CarBuyOrderInfo: Exception expected", canCreate(validParams.buyOrderInfo(null))); + } + + @Test + public void semanticValidity(){ + // Test 1: create semantically valid transaction + assertTrue("Transaction expected to be semantically Valid", + create(validParams).semanticValidity()); + + // Test 2: create semantically invalid transaction - inputs list contains duplicates + byte[] boxId = getRandomBoxId(); + assertFalse("Transaction expected to be semantically Invalid", + create(validParams.inputsIds(Arrays.asList(boxId, boxId))).semanticValidity()); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/functionality/CarDeclarationTransactionTest.java b/src/test/java/io/horizen/lambo/transactions/functionality/CarDeclarationTransactionTest.java new file mode 100644 index 0000000..6caa55a --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/functionality/CarDeclarationTransactionTest.java @@ -0,0 +1,42 @@ +package io.horizen.lambo.transactions.functionality; + +import io.horizen.lambo.transactions.fixtures.parameters.CarDeclarationParameters; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +import static io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters.canCreate; +import static io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters.create; +import static io.horizen.lambo.transactions.fixtures.BoxFixtures.*; +import static org.junit.Assert.*; + + +public class CarDeclarationTransactionTest { + + CarDeclarationParameters validParams; + + @Before + public void initialiazeParams(){ validParams = new CarDeclarationParameters(); } + + @Test + public void creation(){ + // Tests for the common functionality + AbstractRegularTransactionTest.creation(validParams); + + // CarDeclaration specific test: carBoxData is null + assertFalse("CarBoxData: Exception expected", canCreate(validParams.boxData(null))); + } + + @Test + public void semanticValidity(){ + // Test 1: create semantically valid transaction + assertTrue("Transaction expected to be semantically Valid", + create(validParams).semanticValidity()); + + // Test 2: create semantically invalid transaction - inputs list contains duplicates + byte[] boxId = getRandomBoxId(); + assertFalse("Transaction expected to be semantically Invalid", + create(validParams.inputsIds(Arrays.asList(boxId, boxId))).semanticValidity()); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/functionality/SellCarTransactionTest.java b/src/test/java/io/horizen/lambo/transactions/functionality/SellCarTransactionTest.java new file mode 100644 index 0000000..0b09e45 --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/functionality/SellCarTransactionTest.java @@ -0,0 +1,42 @@ +package io.horizen.lambo.transactions.functionality; + +import io.horizen.lambo.transactions.fixtures.parameters.SellCarParameters; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +import static io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters.canCreate; +import static io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters.create; +import static io.horizen.lambo.transactions.fixtures.BoxFixtures.*; +import static org.junit.Assert.*; + + +public class SellCarTransactionTest { + + SellCarParameters validParams; + + @Before + public void initialiazeParams(){ validParams = new SellCarParameters(); } + + @Test + public void creation(){ + // Tests for the common functionality + AbstractRegularTransactionTest.creation(validParams); + + // SellCar specific test: sellOrderInfo is null + assertFalse("CarSellOrderInfo: Exception expected", canCreate(validParams.sellOrderInfo(null))); + } + + @Test + public void semanticValidity(){ + // Test 1: create semantically valid transaction + assertTrue("Transaction expected to be semantically Valid", + create(validParams).semanticValidity()); + + // Test 2: create semantically invalid transaction - inputs list contains duplicates + byte[] boxId = getRandomBoxId(); + assertFalse("Transaction expected to be semantically Invalid", + create(validParams.inputsIds(Arrays.asList(boxId, boxId))).semanticValidity()); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/serialization/BuyCarTransactionSerializationTest.java b/src/test/java/io/horizen/lambo/transactions/serialization/BuyCarTransactionSerializationTest.java new file mode 100644 index 0000000..cf6b4a9 --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/serialization/BuyCarTransactionSerializationTest.java @@ -0,0 +1,17 @@ +package io.horizen.lambo.transactions.serialization; + +import io.horizen.lambo.car.transaction.BuyCarTransaction; +import io.horizen.lambo.transactions.fixtures.parameters.BuyCarParameters; +import org.junit.Test; + +import static io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters.create; +import static org.junit.Assert.assertArrayEquals; + +public class BuyCarTransactionSerializationTest { + @Test + public void serialization(){ + byte[] bytes = create(new BuyCarParameters()).bytes(); + assertArrayEquals("Serialization-parsing-serialization is not correct", + BuyCarTransaction.parseBytes(bytes).bytes(), bytes); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/serialization/CarDeclarationTransactionSerializerTest.java b/src/test/java/io/horizen/lambo/transactions/serialization/CarDeclarationTransactionSerializerTest.java new file mode 100644 index 0000000..fe8ad91 --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/serialization/CarDeclarationTransactionSerializerTest.java @@ -0,0 +1,17 @@ +package io.horizen.lambo.transactions.serialization; + +import io.horizen.lambo.car.transaction.CarDeclarationTransaction; +import io.horizen.lambo.transactions.fixtures.parameters.CarDeclarationParameters; +import org.junit.Test; + +import static io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters.create; +import static org.junit.Assert.assertArrayEquals; + +public class CarDeclarationTransactionSerializerTest { + @Test + public void serialization(){ + byte[] bytes = create(new CarDeclarationParameters()).bytes(); + assertArrayEquals("Serialization-parsing-serialization is not correct", + CarDeclarationTransaction.parseBytes(bytes).bytes(), bytes); + } +} diff --git a/src/test/java/io/horizen/lambo/transactions/serialization/SellCarTransactionSerializationTest.java b/src/test/java/io/horizen/lambo/transactions/serialization/SellCarTransactionSerializationTest.java new file mode 100644 index 0000000..3344d91 --- /dev/null +++ b/src/test/java/io/horizen/lambo/transactions/serialization/SellCarTransactionSerializationTest.java @@ -0,0 +1,17 @@ +package io.horizen.lambo.transactions.serialization; + +import io.horizen.lambo.car.transaction.SellCarTransaction; +import io.horizen.lambo.transactions.fixtures.parameters.SellCarParameters; +import org.junit.Test; + +import static io.horizen.lambo.transactions.fixtures.parameters.AbstractParameters.create; +import static org.junit.Assert.assertArrayEquals; + +public class SellCarTransactionSerializationTest { + @Test + public void serialization(){ + byte[] bytes = create(new SellCarParameters()).bytes(); + assertArrayEquals("Serialization-parsing-serialization is not correct", + SellCarTransaction.parseBytes(bytes).bytes(), bytes); + } +}